#include "board.h"
#include "stm32h7xx_hal_mmc.h"
#include <fcntl.h>
#include <rtthread.h>
#include <stdlib.h>
#include <unistd.h>

#define MMC_MIN_RWB 8

extern void MX_SDMMC1_MMC_Init(void);
extern MMC_HandleTypeDef hmmc1;
static MMC_HandleTypeDef* hmmc = &hmmc1;

static int mmc_info(int argc, char* argv[])
{
    MX_SDMMC1_MMC_Init();
    rt_kprintf("GetCardState ...\n");
    HAL_MMC_CardStateTypeDef state = HAL_MMC_GetCardState(hmmc);
    rt_kprintf("mmc state=%x\n", state);

    rt_kprintf("GetCardCID ...\n");
    HAL_MMC_CardCIDTypeDef cardCID = {0};
    if (HAL_MMC_GetCardCID(hmmc, &cardCID) == HAL_OK) {
        rt_kprintf("ManufacturerID: %02X\n", cardCID.ManufacturerID);
        rt_kprintf("OEM_AppliID: %X\n", cardCID.OEM_AppliID);
        rt_kprintf("ProdRev: %02X\n", cardCID.ProdRev);
        rt_kprintf("ProdSN: %X\n", cardCID.ProdSN);
    } else {
        rt_kprintf("FAIL!\n");
    }

    rt_kprintf("GetCardCSD ...\n");
    HAL_MMC_CardCSDTypeDef cardCSD = {0};
    if (HAL_MMC_GetCardCSD(hmmc, &cardCSD) == HAL_OK) {
        rt_kprintf("DeviceSize: %d\n", cardCSD.DeviceSize);
        rt_kprintf("RdBlockLen: %d\n", cardCSD.RdBlockLen);
        rt_kprintf("MaxBusClkFrec: %d\n", cardCSD.MaxBusClkFrec);
        rt_kprintf("MaxWrBlockLen: %d\n", cardCSD.MaxWrBlockLen);
    } else {
        rt_kprintf("FAIL!\n");
    }

    rt_kprintf("GetCardInfo ...\n");
    HAL_MMC_CardInfoTypeDef cardInfo = {0};
    if (HAL_MMC_GetCardInfo(hmmc, &cardInfo) == HAL_OK) {
        rt_kprintf("CardType: %d\n", cardInfo.CardType);
        rt_kprintf("Class: %d\n", cardInfo.Class);
        rt_kprintf("RelCardAdd: %d\n", cardInfo.RelCardAdd);
        rt_kprintf("BlockNbr: %d\n", cardInfo.BlockNbr);
        rt_kprintf("CardBlockSize: %d\n", cardInfo.BlockSize);
        rt_kprintf("  Capacity(B): %lu\n", ((unsigned long)cardInfo.BlockSize) * cardInfo.BlockNbr);
        rt_kprintf("LogBlockNbr: %d\n", cardInfo.LogBlockNbr);
        rt_kprintf("LogBlockSize: %d\n", cardInfo.LogBlockSize);
    } else {
        rt_kprintf("FAIL!\n");
    }

    return 0;
}
MSH_CMD_EXPORT(mmc_info, eMMC info);

static int mmc_erase(int argc, char* argv[])
{
    MMC_HandleTypeDef* hmmc = &hmmc1;

    rt_uint32_t start_block = argc > 1 ? atoi(argv[1]) : 0;
    rt_uint32_t end_block = argc > 2 ? atoi(argv[2]) : 1;

    rt_kprintf("erase blocks [%d, %d) ... ", start_block, end_block);
    if (HAL_MMC_Erase(hmmc, start_block, end_block) == HAL_OK) {
        rt_kprintf("OK!\n");
    } else {
        rt_kprintf("FAIL!\n");
    }

    return 0;
}
MSH_CMD_EXPORT(mmc_erase, eMMC erase);

#define HEXDUMP_WIDTH 16
static void hexdump(const void* buffer, long size)
{
    long i, j;
    const rt_uint8_t* data = (const rt_uint8_t*)buffer;
    rt_kprintf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n");
    for (i = 0; i < size; i += HEXDUMP_WIDTH) {
        rt_kprintf("[%08x] ", i);
        /* dump hex */
        for (j = 0; j < HEXDUMP_WIDTH; j++) {
            if (i + j < size) {
                rt_kprintf("%02X ", data[i + j]);
            } else {
                rt_kprintf("   ");
            }
        }
        rt_kprintf("\n");
    }
    rt_kprintf("\n");
}

static int mmc_read(int argc, char* argv[])
{
    MMC_HandleTypeDef* hmmc = &hmmc1;

    rt_uint32_t addr = argc > 1 ? atoi(argv[1]) : 0;
    rt_uint32_t blocks = argc > 2 ? atoi(argv[2]) : 1;
    rt_uint32_t block_size = MMC_BLOCKSIZE;

    rt_uint32_t buffer_size = blocks * block_size;
    rt_uint8_t* buffer = rt_malloc(buffer_size);
    if (buffer == RT_NULL) {
        rt_kprintf("alloc buffer %u failed!\n", buffer_size);
        return -1;
    }
    rt_memset(buffer, 0, buffer_size);

    rt_kprintf("read blocks [%d, %d) ... ", addr, addr + blocks);
    if (HAL_MMC_ReadBlocks(hmmc, buffer, addr, blocks, 12000) == HAL_OK) {
        rt_kprintf("OK!\n");
    } else {
        rt_kprintf("FAIL!\n");
    }
    hexdump(buffer, buffer_size);

    rt_free(buffer);
    return 0;
}
MSH_CMD_EXPORT(mmc_read, eMMC read);

static int mmc_write(int argc, char* argv[])
{
    MMC_HandleTypeDef* hmmc = &hmmc1;

    rt_uint32_t addr = argc > 1 ? atoi(argv[1]) : 0;
    rt_uint32_t blocks = argc > 2 ? atoi(argv[2]) : 1;
    rt_uint32_t value0 = argc > 3 ? atoi(argv[3]) : 0;
    rt_uint32_t block_size = MMC_BLOCKSIZE;

    rt_uint32_t buffer_size = blocks * block_size;
    rt_uint8_t* buffer = rt_malloc(buffer_size);
    if (buffer == RT_NULL) {
        rt_kprintf("alloc buffer %u failed!\n", buffer_size);
        return -1;
    }

    rt_kprintf("fill dummy data ...\n");
    for (int i = 0; i < buffer_size; i++) {
        buffer[i] = ((i + value0) & 0xFF);
    }
    hexdump(buffer, buffer_size);

    rt_kprintf("%s: hmmc=%p\n", __func__, hmmc);
    rt_kprintf("write blocks [%d, %d) ... ", addr, addr + blocks);
    if (HAL_MMC_WriteBlocks(hmmc, buffer, addr, blocks, 12000) == HAL_OK) {
        rt_kprintf("OK!\n");
    } else {
        rt_kprintf("FAIL!\n");
    }

    rt_free(buffer);
    return 0;
}
MSH_CMD_EXPORT(mmc_write, eMMC write);

int mmc_bench(int argc, char* argv[])
{
    int i;
    rt_base_t start_tick, end_tick, cost_tick;
    MMC_HandleTypeDef* hmmc = &hmmc1;
    rt_uint32_t block_count = argc > 1 ? atoi(argv[1]) : 8;
    block_count = block_count / MMC_MIN_RWB * MMC_MIN_RWB;
    if (block_count < MMC_MIN_RWB) {
        block_count = MMC_MIN_RWB;
    }

    rt_uint32_t buffer_size = MMC_BLOCKSIZE * MMC_MIN_RWB;
    rt_uint8_t* block_data = rt_malloc(buffer_size);
    if (block_data == RT_NULL) {
        rt_kprintf("alloc block data failed!\n");
        return -1;
    }

    rt_kprintf("fill block data ...\n");
    for (i = 0; i < buffer_size; i++) {
        block_data[i] = 0xFF - (i & 0xFF);
    }

    rt_uint8_t* read_data = rt_malloc(MMC_BLOCKSIZE * MMC_MIN_RWB);
    if (read_data == RT_NULL) {
        rt_kprintf("alloc read data failed!\n");
        rt_free(block_data);
        return -1;
    }

    rt_kprintf("MMC erase ... ");
    start_tick = rt_tick_get();
    // rt_enter_critical();
    if (HAL_MMC_Erase(hmmc, 0, block_count) != HAL_OK) {
        rt_kprintf("[%d]: MMC erase failed!\n", i);
    }
    // rt_exit_critical();
    end_tick = rt_tick_get();
    rt_kprintf("DONE!\n");
    HAL_Delay(10);

    cost_tick = end_tick - start_tick;
    rt_kprintf("MMC Erase summary:\n");
    rt_kprintf("    total blocks : %d\n", block_count);
    rt_kprintf("    total bytes  : %d\n", block_count * MMC_BLOCKSIZE);
    rt_kprintf("    total ticks  : %d\n", cost_tick);

    rt_kprintf("MMC write ... ");
    start_tick = rt_tick_get();
    for (i = 0; i < block_count / MMC_MIN_RWB; i++) {
        if (HAL_MMC_WriteBlocks(hmmc, block_data, i * MMC_MIN_RWB, MMC_MIN_RWB, 12000) != HAL_OK) {
            rt_kprintf("[%d]: MMC write failed!\n", i);
        }
    }
    end_tick = rt_tick_get();
    rt_kprintf("DONE!\n");

    cost_tick = end_tick - start_tick;
    rt_kprintf("MMC Write summary:\n");
    rt_kprintf("    total blocks : %d\n", block_count);
    rt_kprintf("    total bytes  : %d\n", block_count * MMC_BLOCKSIZE);
    rt_kprintf("    total ticks  : %d\n", cost_tick);

    rt_kprintf("MMC read ... ");
    start_tick = rt_tick_get();
    for (i = 0; i < block_count / MMC_MIN_RWB; i++) {
        if (HAL_MMC_ReadBlocks(hmmc, read_data, i * MMC_MIN_RWB, MMC_MIN_RWB, 12000) != HAL_OK) {
            rt_kprintf("[%d]: MMC read failed!\n", i);
        }
        if (memcmp(read_data, block_data, MMC_BLOCKSIZE) != 0) {
            rt_kprintf("[%d]: MMC read out data mismatch write in!\n", i);
        }
    }
    end_tick = rt_tick_get();
    rt_kprintf("DONE!\n");

    cost_tick = end_tick - start_tick;
    rt_kprintf("MMC Read summary:\n");
    rt_kprintf("    total blocks : %d\n", block_count);
    rt_kprintf("    total bytes  : %d\n", block_count * MMC_BLOCKSIZE);
    rt_kprintf("    total ticks  : %d\n", cost_tick);

    HAL_MMC_DeInit(&hmmc1);

    rt_free(read_data);
    rt_free(block_data);
    return 0;
}
MSH_CMD_EXPORT(mmc_bench, eMMC benchmark);

int fs_bench(int argc, char* argv[])
{
    int i, last, step;
    rt_base_t start_tick, end_tick, cost_tick;
    MMC_HandleTypeDef* hmmc = &hmmc1;
    rt_uint32_t block_count = argc > 1 ? atoi(argv[1]) : 1000;

    rt_uint32_t io_block = 8;
    rt_uint32_t io_size = MMC_BLOCKSIZE * io_block;
    rt_uint8_t* block_data = rt_malloc(io_size + 4);
    if (block_data == RT_NULL) {
        rt_kprintf("alloc block data failed!\n");
        return -1;
    }
    rt_memset(block_data, 0, io_size);

    rt_kprintf("fill block data ...\n");
    char* wp = block_data;
    for (i = 0; i < (io_size / 32); i++) {
        // 10 + 2*10 + 2 == 32
        char* line = wp;
        rt_sprintf(wp, "% 8X: ", i * 16);
        wp += 10;
        for (int j = 0; j < 10; j++) {
            rt_sprintf(wp, "%02X", i * 16 + j);
            wp += 2;
        }
        rt_sprintf(wp, "\r\n");
        wp += 2;
        *wp = '\0';
        // rt_kprintf("%s", line);
    }
    rt_kprintf("wp writen: %d\n", wp - (char*)block_data);

    const char* test_fn = "test.data";
    int fd = open(test_fn, O_RDWR | O_CREAT, 0);
    if (fd < 0) {
        rt_kprintf("open file failed!\n");
        rt_free(block_data);
        return -1;
    }

    rt_kprintf("FS write %s:\n", test_fn);
    start_tick = rt_tick_get();
    char head[16];
    last = 0;
    step = block_count / io_block / 100;
    for (i = 0; i < block_count / io_block; i++) {
        rt_snprintf(head, sizeof(head), "%08X: ", i * io_size);
        rt_memcpy(block_data, head, 10);
        long retval = write(fd, block_data, io_size);
        if (retval != io_size) {
            rt_kprintf("[%d]: MMC write failed, %d!\n", i, retval);
        }
        if (i - last > step) {
            rt_kprintf(".");
            last = i;
        }
    }
    end_tick = rt_tick_get();
    rt_kprintf("DONE!\n");

    cost_tick = end_tick - start_tick;
    rt_kprintf("MMC Write summary:\n");
    rt_kprintf("    total blocks : %d\n", block_count);
    rt_kprintf("    total bytes  : %d\n", block_count * MMC_BLOCKSIZE);
    rt_kprintf("    total ticks  : %d\n", cost_tick);

    if (close(fd) < 0) {
        rt_kprintf("close file failed!\n");
    }

    rt_uint8_t* read_data = rt_malloc(io_size + 4);
    if (read_data == RT_NULL) {
        rt_kprintf("alloc read data failed!\n");
        rt_free(block_data);
        return -1;
    }

    // reopen file
    fd = open("test.data", O_RDWR | O_CREAT, 0);

    rt_kprintf("MMC read %s:\n", test_fn);
    start_tick = rt_tick_get();
    last = 0;
    for (i = 0; i < block_count / io_block; i++) {
        rt_snprintf(head, sizeof(head), "%08X: ", i * io_size);
        rt_memcpy(block_data, head, 10);
        long retval = read(fd, read_data, io_size);
        if (retval != io_size) {
            rt_kprintf("[%d]: MMC read failed, %d!\n", i, retval);
        }
        if (memcmp(read_data, block_data, io_size) != 0) {
            rt_kprintf("[%d]: MMC read out data mismatch write in!\n", i);
        }
        if (i - last > step) {
            rt_kprintf(".");
            last = i;
        }
    }
    end_tick = rt_tick_get();
    rt_kprintf("DONE!\n");

    cost_tick = end_tick - start_tick;
    rt_kprintf("MMC Read summary:\n");
    rt_kprintf("    total blocks : %d\n", block_count);
    rt_kprintf("    total bytes  : %d\n", block_count * MMC_BLOCKSIZE);
    rt_kprintf("    total ticks  : %d\n", cost_tick);

    rt_free(read_data);
    rt_free(block_data);

    if (close(fd) < 0) {
        rt_kprintf("close file failed!\n");
    }
    return 0;
}
MSH_CMD_EXPORT(fs_bench, eMMC benchmark);

// #include "lighthouse_png.c"
// #include "red_leave_png.c"
#include "hzk16.c"

struct {
    const char* name;
    const unsigned long size;
    const unsigned char* data;
} bin_files[] = {
#ifdef lighthouse_png_size
    {
        lighthouse_png_name,
        lighthouse_png_size,
        lighthouse_png_data,
    },
#endif
#ifdef red_leave_png_size
    {
        red_leave_png_name,
        red_leave_png_size,
        red_leave_png_data,
    },
#endif
#ifdef hzk16_size
    {
        hzk16_name,
        hzk16_size,
        hzk16_data,
    },
#endif
    {
        0,
        0,
        0,
    }
};

int fs_restore(int argc, char* argv[])
{
    int n = argc > 1 ? atoi(argv[1]) : 0;
    if (n >= sizeof(bin_files) / sizeof(bin_files[0])) {
        n = 0;
    }

    const char* name = bin_files[n].name;
    unsigned long size = bin_files[n].size;
    const unsigned char* data = bin_files[n].data;

    if (name == RT_NULL) {
        rt_kprintf("invalid name!\n");
        return -1;
    }
    rt_kprintf("restore %s with %d bytes ...\n", name, size);

    int fd = open(name, O_CREAT | O_RDWR, 0);
    if (fd < 0) {
        rt_kprintf("open %s failed, %d!\n", name, fd);
        return -1;
    }
    rt_kprintf("open %s success!\n", name);

    rt_uint32_t buffer_size = 4096;
    rt_uint8_t* buffer = rt_malloc(buffer_size);
    if (buffer == RT_NULL) {
        rt_kprintf("alloc buffer %d failed!\n");
        close(fd);
        return -1;
    }

    rt_uint32_t start_tick = rt_tick_get();
    long writen = 0;
    while (writen < size) {
        long rest_size = size - writen;
        rt_uint32_t copy_size = rest_size >= buffer_size ? buffer_size : rest_size;
        rt_memcpy(buffer, data + writen, copy_size);
        rt_kprintf("write %s at %d with %d bytes ...\n", name, writen, copy_size);
        long retval = write(fd, buffer, copy_size);
        if (retval < 0) {
            rt_kprintf("write %s %d failed, %d\n", name, writen, retval);
            break;
        }
        writen += retval;
    }
    rt_uint32_t cost_tick = rt_tick_get() - start_tick;
    rt_kprintf("write %s %d/%d done!\n", name, writen, size);
    rt_kprintf("cost %d ticks!\n", cost_tick);

    rt_free(buffer);
    close(fd);
    return 0;
}
MSH_CMD_EXPORT(fs_restore, restore file);
